﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Reflection;
using System.Text;
using System.Windows.Forms;
using VistaKeysExtender.Properties;
using VistaKeysExtender.WinApi;

namespace VistaKeysExtender
{
    public partial class Form : System.Windows.Forms.Form
    {
        #region User Interface

        private bool IsLoaded { get; set;}

        private readonly IntPtr _currentHandle; 

        public Form(bool showNotifyIcon)
        {
            InitializeComponent();
            notifyIcon.Visible = showNotifyIcon;
            _currentHandle = GetCurrent();
            RegisterHotKeys();
        }

        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);

            lblVersion.Text = string.Format("v. {0}", Assembly.GetExecutingAssembly().GetName().Version);
            LoadData();
        }

        protected override void OnActivated(EventArgs e)
        {
            base.OnActivated(e);

            if (!IsLoaded)
            {
                IsLoaded = true;
                Hide();
            }
        }

        private void closeToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Close();
        }

        private void linkMail_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
            Process.Start(linkMail.Text);
        }

        private void notifyIcon_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            Show();
            Activate();
        }

        private void btnOk_Click(object sender, EventArgs e)
        {
            Hide();
            SaveData();
            MessageBox.Show(this, "Restart application to take affect.", "Keys Extenders");
        }

        private void showToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Show();
            Activate();
        }

        private void btnCancel_Click(object sender, EventArgs e)
        {
            Hide();
            LoadData();
        }

        private void checkBoxMEnabled_CheckedChanged(object sender, EventArgs e)
        {
            UpdateStatusMovedControls();
        }

        private void UpdateStatusMovedControls()
        {
            label7.Enabled =
                numericUpDownmStep.Enabled =
                checkBoxMAlt.Enabled =
                checkBoxMCtrl.Enabled =
                checkBoxMShift.Enabled = label6.Enabled = label5.Enabled = checkBoxMEnabled.Checked;
        }

        private void checkBoxAEnabled_CheckedChanged(object sender, EventArgs e)
        {
            UpdateStatusAttachedControls();
        }

        private void UpdateStatusAttachedControls()
        {
            checkBoxAAlt.Enabled =
                checkBoxACtrl.Enabled =
                checkBoxAShift.Enabled = label3.Enabled = label4.Enabled = checkBoxAEnabled.Checked;
        }

        private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
            Process.Start(linkLabel1.Text);
        }

        #endregion

        #region Settings Data

        private void LoadData()
        {
            checkBoxAEnabled.Checked = Settings.Default.WindowsAttachedEnable;
            checkBoxMEnabled.Checked = Settings.Default.WindowsMoveEnable;
            checkBoxAAlt.Checked = Settings.Default.WindowsAttachedAlt;
            checkBoxAShift.Checked = Settings.Default.WindowsAttachedShift;
            checkBoxACtrl.Checked = Settings.Default.WindowsAttachedCtrl;
            checkBoxMAlt.Checked = Settings.Default.WindowsMoveAlt;
            checkBoxMShift.Checked = Settings.Default.WindowsMoveShift;
            checkBoxMCtrl.Checked = Settings.Default.WindowsMoveCtrl;
            numericUpDownmStep.Value = Settings.Default.WindowsMoveStep;
            checkBoxSEnabled.Checked = Settings.Default.WindowsScreenEnable;
            checkBoxSAlt.Checked = Settings.Default.WindowsScreenAlt;
            checkBoxSShift.Checked = Settings.Default.WindowsScreenShift;
            checkBoxSCtrl.Checked = Settings.Default.WindowsScreenCtrl;
            UpdateStatusMovedControls();
            UpdateStatusAttachedControls();
        }

        private void SaveData()
        {
            Settings.Default.WindowsAttachedEnable = checkBoxAEnabled.Checked;
            Settings.Default.WindowsMoveEnable = checkBoxMEnabled.Checked;
            Settings.Default.WindowsAttachedAlt = checkBoxAAlt.Checked;
            Settings.Default.WindowsAttachedShift = checkBoxAShift.Checked;
            Settings.Default.WindowsAttachedCtrl = checkBoxACtrl.Checked;
            Settings.Default.WindowsMoveAlt = checkBoxMAlt.Checked;
            Settings.Default.WindowsMoveShift = checkBoxMShift.Checked;
            Settings.Default.WindowsMoveCtrl = checkBoxMCtrl.Checked;
            Settings.Default.WindowsMoveStep = (int)numericUpDownmStep.Value;
            Settings.Default.WindowsScreenEnable = checkBoxSEnabled.Checked;
            Settings.Default.WindowsScreenAlt = checkBoxSAlt.Checked;
            Settings.Default.WindowsScreenShift = checkBoxSShift.Checked;
            Settings.Default.WindowsScreenCtrl = checkBoxSCtrl.Checked;
            Settings.Default.Save();
        }

        #endregion

        #region Hot keys 

        private readonly Keys[] _keys = new[] { Keys.Left, Keys.Right, Keys.Up, Keys.Down };

        private readonly IList<HotKey> _hotKeys = new List<HotKey>();

        private void RegisterHotKeys()
        {
            if (Settings.Default.WindowsAttachedEnable)
                RegisterAttached();
            if (Settings.Default.WindowsMoveEnable)
                RegisterMoved();
            if (Settings.Default.WindowsScreenEnable)
                RegisterScreen();
        }

        private void RegisterAttached()
        {
            HotKey.KeyModifiers modifiers = HotKey.KeyModifiers.Windows |
                                            (Settings.Default.WindowsAttachedAlt
                                                 ? HotKey.KeyModifiers.Alt
                                                 : HotKey.KeyModifiers.None) |
                                            (Settings.Default.WindowsAttachedCtrl
                                                 ? HotKey.KeyModifiers.Control
                                                 : HotKey.KeyModifiers.None) |
                                            (Settings.Default.WindowsAttachedShift
                                                 ? HotKey.KeyModifiers.Shift
                                                 : HotKey.KeyModifiers.None);
            Register(modifiers);
        }

        private void RegisterMoved()
        {
            HotKey.KeyModifiers modifiers = HotKey.KeyModifiers.Windows |
                                            (Settings.Default.WindowsMoveAlt
                                                 ? HotKey.KeyModifiers.Alt
                                                 : HotKey.KeyModifiers.None) |
                                            (Settings.Default.WindowsMoveCtrl
                                                 ? HotKey.KeyModifiers.Control
                                                 : HotKey.KeyModifiers.None) |
                                            (Settings.Default.WindowsMoveShift
                                                 ? HotKey.KeyModifiers.Shift
                                                 : HotKey.KeyModifiers.None);
            Register(modifiers);
        }

        private void RegisterScreen()
        {
            HotKey.KeyModifiers modifiers = HotKey.KeyModifiers.Windows |
                                            (Settings.Default.WindowsScreenAlt
                                                 ? HotKey.KeyModifiers.Alt
                                                 : HotKey.KeyModifiers.None) |
                                            (Settings.Default.WindowsScreenCtrl
                                                 ? HotKey.KeyModifiers.Control
                                                 : HotKey.KeyModifiers.None) |
                                            (Settings.Default.WindowsScreenShift
                                                 ? HotKey.KeyModifiers.Shift
                                                 : HotKey.KeyModifiers.None);
            Register(modifiers);
        }

        private void Register(HotKey.KeyModifiers modifiers)
        {
            foreach (Keys key in _keys)
            {
                try
                {
                    HotKey hotKey = new HotKey
                    {
                        Key = key,
                        KeyModifier = modifiers,
                        Handle = _currentHandle
                    };
                    hotKey.HotKeyPressed += HotKeyPressed;
                    _hotKeys.Add(hotKey);
                }
                catch
                {
                    MessageBox.Show(this,
                                    string.Format(
                                        "Cannot register this hot key '{0} + {1}', maybe already registered by another process.",
                                        modifiers, key), "Keys Extenders");
                }
            }
        }

        private static IntPtr GetCurrent()
        {
            using (Process curProcess = Process.GetCurrentProcess())
            using (ProcessModule curModule = curProcess.MainModule)
            {
                if (curModule != null) return curModule.BaseAddress;
            }
            return IntPtr.Zero;
        }

        static void HotKeyPressed(object sender, KeyEventArgs e)
        {
            if (Settings.Default.WindowsAttachedAlt == e.Alt
                    && Settings.Default.WindowsAttachedCtrl == e.Control
                    && Settings.Default.WindowsAttachedShift == e.Shift)
                SetWindowLocation(e.KeyCode);
            else if (Settings.Default.WindowsMoveAlt == e.Alt
                    && Settings.Default.WindowsMoveCtrl == e.Control
                    && Settings.Default.WindowsMoveShift == e.Shift)
                MoveWindow(e.KeyCode);
            else if (Settings.Default.WindowsScreenAlt == e.Alt
                    && Settings.Default.WindowsScreenCtrl == e.Control
                    && Settings.Default.WindowsScreenShift == e.Shift)
                ScreenWindow(e.KeyCode);
        }

        #endregion

        #region Windows moved

        enum WinState
        {
            Normal,
            Minimized,
            Left,
            Right,
            Maximized
        }

        /// <summary>
        /// Get Current Window State
        /// </summary>
        /// <param name="screen"></param>
        /// <param name="rect"></param>
        /// <returns></returns>
        private static WinState GetWindowState(Screen screen, WAWindows.Rect rect)
        {
            if (rect.Width >= screen.WorkingArea.Width && rect.Height >= screen.WorkingArea.Height) // Maximized
                return WinState.Maximized;
            if (rect.Top < -screen.WorkingArea.Height && rect.Left < -screen.WorkingArea.Width)
                return WinState.Minimized;

            if (rect.Height >= screen.WorkingArea.Height && Math.Abs(rect.Width - (screen.WorkingArea.Width/2)) <= 1)
            {
                if (rect.Left == screen.WorkingArea.Left)
                    return WinState.Left;
                return WinState.Right;
            }

            return WinState.Normal;
        }

        private static void SetWindowLocation(Keys k)
        {
            //Active Window
            IntPtr window = WAWindows.GetForegroundWindow();
            // Check SIZEBOX Style (Window can sizable)
            bool isResizableWindow = IsResizableWindow(window);
                
            WAWindows.Rect rect;
            if (WAWindows.GetWindowRect(window, out rect))
            {
                Screen screen = Screen.FromHandle(window);
                
                WinState state = GetWindowState(screen, rect);

                if (isResizableWindow && ((k == Keys.Left && state == WinState.Right) || (k == Keys.Right && state == WinState.Left)
                    || (k == Keys.Down && (state == WinState.Maximized || state == WinState.Left || state == WinState.Right))))
                {
                    WAWindows.ShowWindow(window, (int)WAWindows.WindowShowStyle.ShowNormal);

                    // Fixed problem with restore on other screen
                    Screen newScreen = Screen.FromHandle(window);
                    if (!newScreen.Equals(screen))
                    {
                        if (WAWindows.GetWindowRect(window, out rect))
                            MoveToNewScreen(window, newScreen, screen, rect, isResizableWindow, false);
                    }
                }
                else if (k == Keys.Down)
                {
    				WAWindows.ShowWindow(window, (int)WAWindows.WindowShowStyle.ShowMinimized);
                }
                else if (isResizableWindow && k == Keys.Up)
                {
                    if (state == WinState.Minimized)
                        WAWindows.ShowWindow(window, (int)WAWindows.WindowShowStyle.Restore);
                    else
                    {
                        WAWindows.ShowWindow(window, (int) WAWindows.WindowShowStyle.ShowMaximized);
                        
                        if (WAWindows.GetWindowRect(window, out rect))
                        {
                            WAWindows.SetWindowPos(window, WAWindows.HwndTop,
                                                   screen.WorkingArea.Left,
                                                   screen.WorkingArea.Top,
                                                   screen.WorkingArea.Width, screen.WorkingArea.Height, WAWindows.SwpShowWindow);
                        }
                    }
                }
                else if (isResizableWindow)
                {
                    if (k == Keys.Left)
                    {
                        rect.Left = screen.WorkingArea.Left;
                        rect.Right = screen.WorkingArea.Right - screen.WorkingArea.Width/2;
                    }
                    else if (k == Keys.Right)
                    {
                        rect.Left = screen.WorkingArea.Right - screen.WorkingArea.Width/2;
                        rect.Right = screen.WorkingArea.Right;
                    }

                    WAWindows.ShowWindow(window, (int) WAWindows.WindowShowStyle.ShowMaximized);
                    WAWindows.SetWindowPos(window, WAWindows.HwndTop, rect.Left, screen.WorkingArea.Top,
                                           rect.Width, screen.WorkingArea.Bottom, WAWindows.SwpShowWindow);
                }
            }
            
        }

        private static bool IsResizableWindow(IntPtr window)
        {
            // Fix for Google Chrome
            StringBuilder sb = new StringBuilder(100);
            if (WAWindows.GetClassName(window, sb, sb.Capacity) != IntPtr.Zero
                && sb.ToString().StartsWith("Chrome_"))
                return true;

            return ((Int64)WAWindows.GetWindowLongPtr(window, WAWindows.GwlStyle) & WAWindows.WsSizebox)
                   == WAWindows.WsSizebox;
        }

        private static void MoveWindow(Keys keys)
        {
            int moveStep = Settings.Default.WindowsMoveStep;
            //Active Window
            IntPtr window = WAWindows.GetForegroundWindow();
            WAWindows.Rect rect;
            if (WAWindows.GetWindowRect(window, out rect))
            {
                Screen screen = Screen.FromHandle(window);
                WinState state = GetWindowState(screen, rect);

                if (state == WinState.Normal)
                {
                    if (keys == Keys.Left)
                    {
                        rect.Right -= moveStep;
                        rect.Left -= moveStep;
                    }
                    if (keys == Keys.Right)
                    {
                        rect.Right += moveStep;
                        rect.Left += moveStep;
                    }
                    if (keys == Keys.Up)
                    {
                        rect.Bottom -= moveStep;
                        rect.Top -= moveStep;
                    }
                    if (keys == Keys.Down)
                    {
                        rect.Bottom += moveStep;
                        rect.Top += moveStep;
                    }
                    if (rect.Top < screen.WorkingArea.Bottom && rect.Bottom > 0 && rect.Right > 0 &&
                        rect.Left < screen.WorkingArea.Right)
                        WAWindows.SetWindowPos(window, WAWindows.HwndTop, rect.Left, rect.Top, rect.Width, rect.Height,
                                               WAWindows.SwpShowWindow);
                }
            }
        }

        private static void ScreenWindow(Keys keys)
        {
            // Active Window
            IntPtr window = WAWindows.GetForegroundWindow();
            // Window Sreen
            Screen screen = Screen.FromHandle(window);
            // Get Screen To Move
            Screen moveToScreen = GetScreenToMove(screen, keys);
            // Screen To Move exist
            if (moveToScreen != null)
            {
                WAWindows.Rect rect;
                if (WAWindows.GetWindowRect(window, out rect))
                {
                    bool isResizableWindow = IsResizableWindow(window);
                    bool isFullScreenWindow = IsFullScreenWindow(rect, screen);
                    MoveToNewScreen(window, screen, moveToScreen, rect, isResizableWindow, isFullScreenWindow);
                }
            }
        }

        private static bool IsFullScreenWindow(WAWindows.Rect rect, Screen scr)
        {
            return rect.Left == scr.Bounds.Left && rect.Top == scr.Bounds.Top
                   && rect.Width == scr.Bounds.Width && rect.Height == scr.Bounds.Height;
        }

        private static void MoveToNewScreen(IntPtr window, Screen screen, Screen moveToScreen, WAWindows.Rect rect, bool isResizableWindow, bool isFullScreenWindow)
    	{
            Rectangle rectMoveToScreen = isFullScreenWindow ? moveToScreen.Bounds : moveToScreen.WorkingArea;
            Rectangle rectScreen = isFullScreenWindow ? screen.Bounds : screen.WorkingArea;

            double xRatio = ((double) rectMoveToScreen.Width)/((double) rectScreen.Width);
    		double yRatio = ((double) rectMoveToScreen.Height)/((double) rectScreen.Height);

            int x = rectMoveToScreen.Left + (int)((double)(rect.Left - rectScreen.Left) * xRatio);
            int y = rectMoveToScreen.Top + (int)((double)(rect.Top - rectScreen.Top) * yRatio);
            int cx = ((isResizableWindow || isFullScreenWindow) ? (int)((double)rect.Width * xRatio) : rect.Width);
            int cy = ((isResizableWindow || isFullScreenWindow) ? (int)((double)rect.Height * yRatio) : rect.Height);

            // Fix for full screen windows
            if (isFullScreenWindow)
                WAWindows.SetWindowPos(window, WAWindows.HwndTop, x + 1, y + 1, cx - 2, cy - 2, WAWindows.SwpShowWindow);

            WAWindows.SetWindowPos(window, WAWindows.HwndTop, x, y, cx, cy, WAWindows.SwpShowWindow);
    	}

    	private static Screen GetScreenToMove(Screen screen, Keys keys)
        {
            foreach (Screen scr in Screen.AllScreens)
            {
                if (!scr.Equals(screen))
                {
                    switch (keys)
                    {
                        case Keys.Left:
                            if (scr.WorkingArea.Right == screen.WorkingArea.Left
                                && scr.WorkingArea.Top == scr.WorkingArea.Top)
                                return scr;
                                break;
                        case Keys.Right:
                            if (scr.WorkingArea.Left == screen.WorkingArea.Right
                               && scr.WorkingArea.Top == scr.WorkingArea.Top)
                                return scr;
                            break;
                        case Keys.Down:
                            if (scr.WorkingArea.Top == screen.WorkingArea.Bottom
                               && scr.WorkingArea.Left == scr.WorkingArea.Left)
                                return scr;
                            break;
                        case Keys.Up:
                            if (scr.WorkingArea.Bottom == screen.WorkingArea.Top
                               && scr.WorkingArea.Left == scr.WorkingArea.Left)
                                return scr;
                            break;
                    }
                }
            }
            return null;
        }

        #endregion
    }
}
